﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using PSE.AuditLogging.Repository;
using PSE.AuditLogging.LogEntry;
using System.Diagnostics;
using PSE.Framework.Threading;
using PSE.AuditLogging.StorageService.Configuration;
using System.Configuration;
using PSE.AuditLogging.Common.Exception;
using PSE.AuditLogging.Repository.Manager;
using PSE.AuditLogging.Common.Configuration;
using PSE.Framework.ErrorLogging;

namespace PSE.AuditLogging.StorageService.Instance
{
    internal class LogStorer : IDisposable
    {
        private Dictionary<string, PersistentRepositoryManager> _persistentRepositories;
        private TemporaryRepositoryManager _temporaryRepository;
        private CrossProcessManualResetEvent _executionControl;
        private string _productId;
        private string _instanceId;
        private int _logQueueCheckInterval;

        internal LogStorer(string productId, string instanceId, CrossProcessManualResetEvent executionControl)
        {
            _productId = productId;
            _instanceId = instanceId;
            _executionControl = executionControl;
            _persistentRepositories = new Dictionary<string, PersistentRepositoryManager>();
        }

        internal void Execute()
        {
            LogEntryPackage entryPackage;
            BaseLogEntry entry;
            bool signed;

            try
            {
                _logQueueCheckInterval = ConfigurationReader.LogServiceConfiguration.Products[_productId].Instances[_instanceId].TemporaryRepositoryCheckInterval;

                LoadProviders();
            }
            catch (Exception ex)
            {///CRITICAL ERROR
                ///Situacao: um erro nao tratado ocorreu
                //* logar e finalizar execucao
                ErrorLogger.WriteLog(LogType.Error, string.Format("A critical error occurs. The instance '{0}' of the component will be stoped.", _instanceId) + Environment.NewLine + ex.ToString());
                _executionControl.Set();
                return;
            }

            while (!_executionControl.WaitOne(_logQueueCheckInterval))
            {
                try
                {

                    do
                    {
                        entryPackage = null;

                        if (_executionControl.WaitOne(0))
                            break;

                        #region ler um pacote do repositorio temporario
                        try
                        {
                            //recuperar uma mensagem da fila
                            entryPackage = _temporaryRepository.Peek();

                            if (entryPackage == null) //se nao ha mensagens na fila
                                break;//parar o loop interno para aguardar novamente pelo tempo de verificacao da fila
                        }
                        catch (InvalidLogEntryPackageException ex)
                        {///WARNING
                            ///Caso haja um objeto invalido no repositorio
                            ///* Logar e remover o objeto
                            ErrorLogger.WriteLog(LogType.Warning, "An invalid log entry package will be discarded." + Environment.NewLine + ex.ToString());

                            //tentar remover o objeto do repositorio
                            _temporaryRepository.RemoveTop();

                            //passar para a proxima mensagem
                            continue;
                        }
                        #endregion

                        if (_executionControl.WaitOne(0))
                            break;

                        #region Armazenar um log no repositorio persistente
                        try
                        {
                            entry = null;
                            signed = false;

                            try
                            {
                                //validar pacote
                                LogEntryPackageValidator.Validate(entryPackage);
                                signed = true;
                            }
                            catch (InvalidSignatureException ex)
                            {///ERROR
                                ///Situacao: caso o erro seja de assinatura, recuperar o body do pacote e setar flag indicando que o dado nao esta assinado
                                ///* Logar apenas

                                //logar que um log entry sem assinatura foi recebido
                                ErrorLogger.WriteLog(LogType.Warning, ex);
                            }

                            entry = entryPackage.Body;

                            //validar o tipo de log
                            if (!_persistentRepositories.ContainsKey(entry.LogType))
                                throw new InvalidLogTypeException(string.Format("The logType '{0}' its not configured. The log entry package will be discarded.", entry.LogType));

                            //recuperar o repositorio persistente relacionado ao tipo de log
                            IPersistentRepository _persistentRepository = _persistentRepositories[entry.LogType];

                            //enviar para repositorio persistente
                            if (signed)//se dado estiver assinado
                                _persistentRepository.Send(entryPackage);
                            else//se dado nao estiver assinado
                                _persistentRepository.SendUnsigned(entryPackage);

                            //Remover log do repositorio temporario
                            _temporaryRepository.RemoveTop();
                        }
                        catch (InvalidLogEntryPackageException ex)
                        {///WARNING
                            ///Situacao:Uma mensagem que nao pode ser desempacotada, por estar invalida ou corronpida

                            //logar o erro e descartar a mensagem por estar invalida
                            ErrorLogger.WriteLog(LogType.Warning, "An invalid log entry package has been detected. The log entry package was discarded." + Environment.NewLine + ex.ToString());

                            //Remover log do repositorio remporario
                            _temporaryRepository.RemoveTop();

                            //pular para a proxima mensagem
                            continue;
                        }
                        catch (InvalidLogTypeException ex)
                        {///WARNING
                            ///Situacao:Uma mensagem configurada com tipo errado estava na fila

                            //logar o erro e descartar a mensagem por estar invalida
                            ErrorLogger.WriteLog(LogType.Warning, "An invalid log entry package has been detected. The log entry package was discarded." + Environment.NewLine + ex.ToString());

                            //Remover log do repositorio remporario
                            _temporaryRepository.RemoveTop();

                            //pular para a proxima mensagem
                            continue;
                        }
                        catch (Exception ex)
                        {///ERROR
                            ///Situacao: Ocorreu um erro nao tratado
                            ErrorLogger.WriteLog(LogType.Error, "An error occurs when saving log entry on the persistent repository." + Environment.NewLine + ex.ToString());

                            //parar o loop interno
                            break;
                        }
                        #endregion

                    } while (entryPackage != null);
                }
                catch (Exception ex)
                {///ERROR
                    ///Situacao: Ocorreu um erro nao tratado
                    ///* Logar o erro e continuar a execucao do componente
                    ErrorLogger.WriteLog(LogType.Error, "An error occurs when saving log entry on the persistent repository." + Environment.NewLine + ex.ToString());

                    //continuar a execucao
                    continue;
                }
            }
        }

        private void LoadProviders()
        {
            //criar repositorio temporario para a instancia
            _temporaryRepository = new TemporaryRepositoryManager();
            _temporaryRepository.LoadProvider(ConfigurationReader.LogServiceConfiguration.
                Products[_productId].Instances[_instanceId].TemporaryRepositoryProvider);

            //criar repositorios persistentes para cada tipo de log
            foreach (StorageConfigurationElement storage in ConfigurationReader.LogServiceConfiguration.Products[_productId].Instances[_instanceId].Storages)
            {
                PersistentRepositoryManager repository = new PersistentRepositoryManager();
                repository.LoadProvider(storage.RepositoryProvider);

                //adicionar uma entrada no dicionario para cada tipo configurado para o storage, todas as entradas apontam para a mesma instancia
                foreach (LogTypeConfigurationElement logType in storage.LogTypes)
                {
                    _persistentRepositories.Add(logType.Id, repository);
                }
            }
        }

        #region IDisposable Members
        public void Dispose()
        {
            if (_executionControl != null)
                _executionControl.Set();

            if (_temporaryRepository != null)
                _temporaryRepository.Dispose();
        }
        #endregion
    }
}
